package server;

import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

import net.ClientSideConnection;
import net.Connection;
import net.ServerInfo;
import util.Logger;

/**
 * Matches up connected users appropriately. After users have been matched up,
 * they disconnect from the match maker and share a direct connection between
 * each other.
 * 
 * @author Jason
 * 
 */
public class MatchMakingServer extends Server {

	private int minPlayers = 2, maxPlayers = 3;
	private boolean autoStartGames = true;
	private final Queue<Connection> playersQueue = new LinkedList<Connection>();
	private final GameHostProvider provider;

	public MatchMakingServer(GameHostProvider provider) throws IOException {
		super(MATCH_MAKING_PORT);
		if (provider == null)
			throw new NullPointerException("Provider cannot be null.");
		this.provider = provider;
		startServer();
		Logger.log(Logger.NORMAL, "MatchMaking server started on port "
				+ tcpPort);
	}

	public synchronized void setMinimumPlayers(int minPlayers) {
		this.minPlayers = minPlayers;
	}

	public synchronized void setMaximumPlayers(int maxPlayers) {
		this.maxPlayers = maxPlayers;
	}

	public void setAutoStartGames(boolean b) {
		autoStartGames = b;
	}

	@Override
	protected synchronized void connectionReceived(Connection conn) {
		Logger.log(Logger.NORMAL, "A player joined. (" + conn.getIP() + ")");
		playersQueue.add(conn);
		if (autoStartGames) {
			if (attemptMatchMaking() == false) {
				conn.send(QUEUE_POSITION + DELIM + playersQueue.size());
			}
		} else
			conn.send(QUEUE_POSITION + DELIM + playersQueue.size());
	}

	protected synchronized boolean attemptMatchMaking() {
		Logger.log(Logger.FINEST, "Attempting to make matches...");
		if (playersQueue.size() < minPlayers) {
			Logger.log(Logger.FINE, "Not enough players. ("
					+ playersQueue.size() + " out of " + minPlayers + ")");
			return false;
		} else if (provider.hasAvailableServer() == false) {
			Logger.log(Logger.WARNING, "No game server available.");
			return false;
		} else {
			ServerInfo serverAddress = provider.getNextServerAddress();
			if (serverAddress == null) {
				Logger
						.log(Logger.SEVERE,
								"The server provider lied about having an available server.");
				return false;
			}
			// get as many players possible for this next game
			final Connection[] players = new Connection[Math.min(playersQueue
					.size(), maxPlayers)];
			for (int i = 0; i < players.length; i++) {
				players[i] = playersQueue.poll();
				players[i].send(QUEUE_POSITION + DELIM + 0);
			}
			Logger.log(Logger.NORMAL, "Starting a game of " + players.length
					+ " players.");
			startGame(players, serverAddress);
			int spotInLine = 1;
			for (Connection c : playersQueue) {
				c.send(QUEUE_POSITION + DELIM + (spotInLine++));
			}
			return true;
		}
	}

	protected void startGame(final Connection[] players, final ServerInfo srv) {
		final ServerInfo serverAddress = new ServerInfo("76.118.43.38",
				srv.port); // TODO remove
		new Thread(new Runnable() {
			public void run() {
				// first, tell the server the IPs of the players to expect
				ClientSideConnection serverConn = new ClientSideConnection(
						serverAddress);
				serverConn.addTCPConnectionListener(MatchMakingServer.this);
				serverConn.addUDPConnectionListener(MatchMakingServer.this);
				try {
					serverConn.connect();
				} catch (IOException e) {
					Logger
							.log(e,
									"There was an error connecting to the game host server.");
					return;
				}
				StringBuffer sb = new StringBuffer(MAKE_READY);
				for (Connection conn : players) {
					sb.append(DELIM).append(conn.getIP());
				}
				serverConn.send(sb.toString());
				try {
					synchronized (serverConn) {
						serverConn.wait();
					}
				} catch (InterruptedException e) {
				}
				for (Connection player : players) {
					player.send(JOIN_SERVER + DELIM + serverAddress.ip + DELIM
							+ serverAddress.port);
				}
			}
		}).start();
	}

	public void exit() {
		stopServer(true);
		provider.dispose();
		Logger.log(Logger.NORMAL, "MatchMakingServer has exited.");
	}

	@Override
	public void receive(byte[] bytes, Connection conn) {
		String msg = new String(bytes);
		String[] m = msg.split(DELIM);
		String command = m[0];
		if (command.equals(MAKE_READY)) {
			synchronized (conn) {
				conn.notify();
			}
		} else {
			Logger.log(Logger.WARNING, "Do not recognize message: " + msg);
		}
	}

	@Override
	public void tcpConnectionBroken(Connection conn) {
		super.tcpConnectionBroken(conn);
		playersQueue.remove(conn);
	}

}
